package librecalcdataproxy.server;

import librecalcdataproxy.client.CellValue;
import librecalcdataproxy.client.CellsRange;
import librecalcdataproxy.client.LibreCalcDataProxy;
import com.sun.star.table.CellContentType;
import com.sun.star.table.CellRangeAddress;
import com.sun.star.table.XCell;
import com.sun.star.uno.UnoRuntime;
import java.io.*;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Date;

/**
 * @author Wiktor Toporek
 */
public class LibreCalcDataProxyImpl extends UnicastRemoteObject implements LibreCalcDataProxy {

    public final static String RMIServiceName = "LibreCalcDataProxy";
    public final static int RMIDefServicePort = 1604;
    public static int RMIServicePort;
    
    private int docCounter;
    private ArrayList<DocInstance> docInstances;
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws IOException {
        
        RMIServicePort = RMIDefServicePort;
        /*if (args.length>0){
            try{
                RMIServicePort = Integer.parseInt(args[0]);
            }catch(NumberFormatException e){
                System.out.println("Invalid port number in command line argument. Default port (" + RMIDefServicePort + ") will be used.");
            }
        }*/
                
        if (System.getSecurityManager() == null) 
            System.setSecurityManager(new SecurityManager());
        
        try {

            LibreCalcDataProxy engine = new LibreCalcDataProxyImpl();

            Registry registry = LocateRegistry.createRegistry(RMIServicePort);
            registry.rebind(RMIServiceName, engine);
            
            System.out.println("RMI service started successfully");
        } catch (RemoteException e) {
            System.err.println("Error starting RMI service. Exception: " + e);
            e.printStackTrace();
            System.exit(1);
        }

    }
    /*
    public static void stopService() {
        try {
            Registry registry = LocateRegistry.getRegistry(RMIServicePort);
            registry.unbind(RMIServiceName);
        } catch (Exception ex) {
            ex.printStackTrace();
            System.exit(1);
        }
        System.exit(0);
    }*/

    public LibreCalcDataProxyImpl() throws RemoteException {
        super();

        docInstances = new ArrayList<>();
        docCounter = 0;

    }

    private int _openSpreadsheet(String url) {
        try {
            DocInstance doc = new DocInstance(url);
            int freeIndex = docInstances.indexOf(null);
            if (freeIndex == -1) {
                if (docInstances.add(doc)) {
                    ++docCounter;
                    return docInstances.size()-1;
                } else {
                    return -1;
                }
            } else {
                docInstances.set(freeIndex, doc);
                ++docCounter;
                return freeIndex;
            }

        } catch (Exception e) {
            return -1;
        }
    }

    @Override
    public void freeSpreadsheet(int documentIndex) {
        docInstances.set(documentIndex, null);
        System.gc();
        --docCounter;
    }

    @Override
    public void freeAllSpreadsheets() {
        docInstances.clear();
        docCounter = 0;
        
    }

    @Override
    public int[] openedIndexes() {
        int[] ret = new int[docCounter];

        int i = 0;
        for (int j = 0; j < docInstances.size(); ++j) {
            if (docInstances.get(j) != null) {
                ret[i++] = j;
            }
        }

        return ret;
    }

    @Override
    public int openNewSpreadsheet() {
        return _openSpreadsheet("private:factory/scalc");
    }

    @Override
    public int openExistingSpreadsheet(String filepath) {
        return _openSpreadsheet("file:" + filepath);
    }

    private DocInstance _getDoc(int documentIndex) throws IndexOutOfBoundsException {
        if (documentIndex < 0 || documentIndex >= docInstances.size()) {
            throw new IndexOutOfBoundsException("documentIndex out of bounds");
        }

        return docInstances.get(documentIndex);
    }

    @Override
    public boolean selectSheet(int documentIndex, int sheetIndex) {
        return _getDoc(documentIndex).selectSheet(sheetIndex);
    }

    @Override
    public void setCell(int documentIndex, int x, int y, double value) {
        _getDoc(documentIndex).getCell(x, y).setValue(value);
    }

    @Override
    public void setCell(int documentIndex, int x, int y, String value) {
        XCell xCell = _getDoc(documentIndex).getCell(x, y);

        com.sun.star.text.XText xCellText = (com.sun.star.text.XText) UnoRuntime.queryInterface(com.sun.star.text.XText.class, xCell);
        com.sun.star.text.XTextCursor xTextCursor = xCellText.createTextCursor();
        xCellText.setString(value);

    }

    @Override
    public void setCell(int documentIndex, int x, int y, Date value) {
        setCell(documentIndex, x, y, value.toLocaleString());
    }

    @Override
    public boolean hookExportUI(int documentIndex) {
        return _getDoc(documentIndex).hookExportUI();
    }

    @Override
    public void waitForDataExport(int documentIndex) {
        EventExchanger ee = _getDoc(documentIndex).eventExchanger;
        ee.startWaiting();
        ee.reset();
        ee.checkClick();
        ee.stopWaiting();
    }

    @Override
    public CellValue getCellValue(int documentIndex, int x, int y) throws RemoteException {
        XCell xCell = _getDoc(documentIndex).getCell(x, y);
        switch (xCell.getType().getValue()) {
            case CellContentType.VALUE_value:
                return new CellValue(xCell.getValue());
            case CellContentType.TEXT_value:
                com.sun.star.text.XText xCellText = (com.sun.star.text.XText) UnoRuntime.queryInterface(com.sun.star.text.XText.class, xCell);
                return new CellValue(xCellText.getString());
        }
        return new CellValue();
    }

    @Override
    public String getCellValueString(int documentIndex, int x, int y) {
        XCell xCell = _getDoc(documentIndex).getCell(x, y);
        switch (xCell.getType().getValue()) {
            case CellContentType.VALUE_value:
                return Double.toString(xCell.getValue());
            case CellContentType.TEXT_value:
                com.sun.star.text.XText xCellText = (com.sun.star.text.XText) UnoRuntime.queryInterface(com.sun.star.text.XText.class, xCell);
                return xCellText.getString();
            default:
                return xCell.getFormula();
        }
    }

    @Override
    public double getCellValueDouble(int documentIndex, int x, int y) throws NumberFormatException {
        XCell xCell = _getDoc(documentIndex).getCell(x, y);
        if (xCell.getType().getValue() != CellContentType.VALUE_value) {
            throw new NumberFormatException("Fetched cell content type is not a numeric value type.");
        }
        return xCell.getValue();
    }

    @Override
    public CellsRange[] getSelectedCellsRange(int documentIndex) throws RemoteException {
        ArrayList<CellsRange> list = new ArrayList<>();

        CellRangeAddress[] sel = _getDoc(documentIndex).getCurrentSelection();
        for (CellRangeAddress range : sel) {
            list.add(new CellsRange(range.StartColumn, range.StartRow, range.EndColumn, range.EndRow));
        }

        return list.toArray(new CellsRange[0]);
    }
}
